package cn.jtfadmin.activiti.controller.rest;

import cn.jtfadmin.activiti.domain.dto.*;
import cn.jtfadmin.activiti.service.ActFormService;
import cn.jtfadmin.activiti.service.ActModelService;
import cn.jtfadmin.base.lang.common.domain.dto.PageRDTO;
import cn.jtfadmin.base.lang.common.domain.dto.ResultDTO;
import cn.jtfadmin.base.lang.common.utils.RequestUtils;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricProcessInstanceQuery;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * act 历史数据查询controller
 * @author jtf
 * @since 0.0.2
 */
@RestController
@Validated
public class ActivitiHistoryController {

    @Autowired
    private HistoryService historyService;

    @Autowired
    private ActModelService actModelService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private ActFormService actFormService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private ProcessEngine processEngine;

    /**
     * 获取历史流程实例
     * @param modelId 模型id
     * @param tenantId 租户id
     * @param processName 流程定义名称
     * @param pageNo 页码
     * @param pageSize 每页显示数量
     * @return
     */
    @GetMapping(value = "/activiti/history/listHistoryProcessInstance")
    public ResultDTO<PageRDTO<HistoryProcessInstanceDTO>> listHistoryProcessInstance(@RequestParam(required = false) String modelId,
                                                               @RequestParam @NotBlank String tenantId,
                                                               @RequestParam(required = false) String processName,
                                                               @RequestParam(required = false, defaultValue = "1") Integer pageNo,
                                                               @RequestParam(required = false, defaultValue = "10") Integer pageSize){
        List<String> deploymentIds = null;
        if(StringUtils.hasText(modelId)){
            deploymentIds = new ArrayList<>(actModelService.listDeploymentIdsByModelId(modelId));
        }
        long count = createHistoricProcessInstanceQuery(deploymentIds, tenantId, processName).count();
        if(count == 0L){
            return ResultDTO.successData(new PageRDTO<>(Collections.emptyList(), count));
        }
        List<HistoricProcessInstance> historicProcessInstances = createHistoricProcessInstanceQuery(deploymentIds, tenantId, processName).listPage((pageNo - 1) * pageSize, pageSize);
        List<HistoryProcessInstanceDTO> collect = historicProcessInstances.stream().map(this::instanceToDTO).collect(Collectors.toList());
        return ResultDTO.successData(new PageRDTO<>(collect,count));
    }
    /**
     * 将流程实例转换为流程实例dto, 返回前端使用
     * @param t 流程实例
     * @return 流程实例dto
     * @see #listHistoryProcessInstance(String, String, String, Integer, Integer)
     */
    private HistoryProcessInstanceDTO instanceToDTO(HistoricProcessInstance t){
        return new HistoryProcessInstanceDTO(
                t.getId(),
                t.getBusinessKey(),
                t.getProcessDefinitionId(),
                t.getProcessDefinitionName(),
                t.getProcessDefinitionKey(),
                t.getDeploymentId(),
                t.getStartTime(),
                t.getEndTime(),
                t.getDurationInMillis(),
                t.getEndActivityId(),
                t.getStartUserId(),
                t.getStartActivityId(),
                t.getDeleteReason(),
                t.getSuperProcessInstanceId(),
                t.getTenantId(),
                t.getName(),
                t.getDescription(),
                t.getProcessVariables()

        );
    }

    /**
     * 填充表单中的值
     * @param formDTO 表单
     * @param historicVariableInstances 历史表单变量
     * @see #getDetailInfo(String)
     */
    private void fillFormDateValue(FormDTO formDTO, List<HistoricVariableInstance> historicVariableInstances){
        if(formDTO.getFormProperties()!=null){
            if(StringUtils.hasText(formDTO.getTaskId())){
                historicVariableInstances =
                        historicVariableInstances.stream().filter(t->Objects.equals(t.getTaskId(), formDTO.getTaskId())).collect(Collectors.toList());
            }else {
                historicVariableInstances = historicVariableInstances.stream()
                        .filter(t->!StringUtils.hasText(t.getTaskId())).collect(Collectors.toList());
            }
            for (FormDTO.FormPropertyDTO formProperty : formDTO.getFormProperties()) {
                for (HistoricVariableInstance target : historicVariableInstances) {
                    if(Objects.equals(target.getVariableName(), formProperty.getId())){
                        String variableTypeName = target.getVariableTypeName();
                        if(variableTypeName.equals("date") && Objects.nonNull(target.getValue())){
                            formProperty.setValue(new SimpleDateFormat(formProperty.getDatePattern()).format(target.getValue()));
                        }
                        if(Objects.nonNull(target.getValue())){
                            formProperty.setValue(target.getValue().toString());
                        }
                    }
                }
            }
        }

    }
    /**
     * 获取流程实例明细,调用位置:流程里列表
     * @param id 流程实例id
     * @return 流程实例明细
     */
    @GetMapping(value = "/activiti/history/getDetailInfo")
    public ResultDTO<HistoryProcessInstanceDetailDTO> getDetailInfo(@RequestParam @NotBlank String id){
        // 流程实例
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
        if(processInstance == null){
            throw new IllegalArgumentException("流程已删除");
        }
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processInstance.getProcessDefinitionId()).singleResult();

        HistoryProcessInstanceDetailDTO dto = new HistoryProcessInstanceDetailDTO();
        dto.setProcessInstanceDTO(this.instanceToDTO(processInstance));
        List<HistoricVariableInstance> historicVariableInstances = historyService.createHistoricVariableInstanceQuery().processInstanceId(id).list();
        FormDTO startForm = actFormService.getStartFormInfoByDeploymentId(processInstance.getDeploymentId());
        fillFormDateValue(startForm, historicVariableInstances);
        // 此流程实例的所有历史任务
        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery().processInstanceId(id).list();
        dto.setStartFormDTO(startForm);
        List<HistoryProcessInstanceDetailDTO.TaskDTO> taskDTOList = new ArrayList<>(historicTaskInstances.size());
        dto.setTaskDTOList(taskDTOList);
        for (HistoricTaskInstance h : historicTaskInstances) {
            try{
                String taskDefinitionKey = h.getTaskDefinitionKey();
                FormDTO taskForm = actFormService.getTaskFormInfonByProcessDefinitionIdAndTaskDefinitionKey(processDefinition.getId(),
                        taskDefinitionKey, h.getExecutionId());
                if(taskForm != null){
                    taskForm.setTaskId(h.getId());
                }

                fillFormDateValue(taskForm, historicVariableInstances);
                HistoryProcessInstanceDetailDTO.TaskDTO taskDTO = new HistoryProcessInstanceDetailDTO.TaskDTO(
                        h.getDeleteReason(),
                        h.getStartTime(),
                        h.getEndTime(),
                        h.getDurationInMillis(),
                        h.getWorkTimeInMillis(),
                        h.getClaimTime(),
                        h.getId(),
                        h.getName(),
                        h.getDescription(),
                        h.getPriority(),
                        h.getOwner(),
                        h.getAssignee(),
                        h.getProcessInstanceId(),
                        h.getExecutionId(),
                        h.getProcessDefinitionId(),
                        h.getCreateTime(),
                        h.getTaskDefinitionKey(),
                        h.getDueDate(),
                        h.getCategory(),
                        h.getParentTaskId(),
                        h.getTenantId(),
                        h.getFormKey(),
                        h.getTaskLocalVariables(),
                        h.getProcessVariables(),
                        taskForm
                );
                taskDTOList.add(taskDTO);
            }catch (Exception e){
                e.printStackTrace();
            }

        }
        return ResultDTO.successData(dto);
    }

    /**
     * 删除历史流程实例
     * @param id 流程实例id
     * @return
     */
    @DeleteMapping(value = "/activiti/history/delHistoryProcessInstance")
    public ResultDTO<String > del(@RequestParam @NotBlank String id ){
        historyService.deleteHistoricProcessInstance(id);
        return ResultDTO.success();
    }

    /**
     * 删除历史流程实例
     * @param ids 流程实例id,....
     * @return
     */
    @DeleteMapping(value = "/activiti/history/delBatchHistoryProcessInstance")
    @Transactional
    public ResultDTO<String > delBatch(@RequestParam @NotBlank String ids ){
        Set<String> idSet = StringUtils.commaDelimitedListToSet(ids);
        for (String id : idSet) {
            del(id);
        }
        return ResultDTO.success();
    }


    /**
     * 获取流程实例运行图片,调用位置:流程列表，返回内容为输入流图片
     * @param id 流程实例id
     */
    @GetMapping(value = "/activiti/history/getFlowImg", produces = MediaType.IMAGE_PNG_VALUE)
    public void getFlowImg(@RequestParam String id) {
        HttpServletResponse response = RequestUtils.getResponse();
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");//在跨域下前端拿到回应头
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

        try {
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(id+".png", "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // ignore
        }
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
        List<Task> activeTasks=taskService.createTaskQuery().processDefinitionId(processInstance.getProcessDefinitionId()).list();
        // 已执行的节点ID集合
        List<String> executedActivityIdList = new ArrayList();
        List<String> highLines = new ArrayList();
        List<String> highNodes = new ArrayList();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        for(Task tk:activeTasks){
            executedActivityIdList.add(tk.getTaskDefinitionKey());
            highLines.addAll(getHighLines(bpmnModel,tk.getTaskDefinitionKey()));
            highNodes.addAll(getHighNodes(bpmnModel,tk.getTaskDefinitionKey()));
        }
        ProcessEngineConfiguration processEngineConfiguration = processEngine.getProcessEngineConfiguration();


        ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();


        try(InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", highNodes,
                highLines,"宋体","宋体","宋体",null,1.0);
        ){
            StreamUtils.copy(imageStream, RequestUtils.getResponse().getOutputStream());
        }catch (IOException ex){
            throw new RuntimeException(ex);
        }
    }
    /**
     * 获取高亮的线
     * @param bpmnModel 模型
     * @param key 当前节点key
     * @return 高亮的线
     * @see #getFlowImg(String)
     */
    private Set<String> getHighLines(BpmnModel bpmnModel, String key){
        FlowNode fl=(FlowNode) bpmnModel.getFlowElement(key);
        List<SequenceFlow> pvmTransitions = fl.getIncomingFlows();
        Set highLines=new HashSet<>();
        for(SequenceFlow sf:pvmTransitions){
            highLines.add(sf.getId());
            if(StringUtils.hasText(sf.getSourceRef())){
                highLines.addAll(getHighLines(bpmnModel,sf.getSourceRef()));
            }
        }
        return highLines;
    }
    /**
     * 获取高亮的节点
     * @param bpmnModel 模型
     * @param key 当前节点key
     * @return #getFlowImg(String)
     */
    private Set<String> getHighNodes(BpmnModel bpmnModel,String key){
        FlowNode fl=(FlowNode) bpmnModel.getFlowElement(key);
        List<SequenceFlow> sequenceFlows = fl.getIncomingFlows();
        Set<String> highNodes=new HashSet<>();
        highNodes.add(key);
//        for(SequenceFlow sf:sequenceFlows){
//            if(StringUtils.hasText(sf.getSourceRef())){
//                highNodes.addAll(getHighNodes(bpmnModel,sf.getSourceRef()));
//            }
//        }
        return highNodes;
    }


    private HistoricProcessInstanceQuery createHistoricProcessInstanceQuery(
            List<String> deploymentIds,
            String tenantId,
             String processName
    ){

        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        if(StringUtils.hasText(processName)){
            historicProcessInstanceQuery.processDefinitionName(processName);
        }
        if(StringUtils.hasText(tenantId)){
            historicProcessInstanceQuery.processInstanceTenantId(tenantId);
        }
        if(!CollectionUtils.isEmpty(deploymentIds)){
            historicProcessInstanceQuery.deploymentIdIn(deploymentIds);
        }
        historicProcessInstanceQuery.finished();
        return historicProcessInstanceQuery;
    }
}
